/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <stm32_lcd.h>
#include <hal_lcd.h>
#include <x_config.h>
#include CONFIG_STM32_HAL_H
/* includes (standard library, system) ---------------------------------------*/
#include <string.h>
#include <stdlib.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_module.h>
#include <hal_it.h>

/* defines -------------------------------------------------------------------*/

/* LCD display enable pin */
#define DSI_BL_CTRL_GPIO_PORT           GPIOI
#define DSI_BL_CTRL_GPIO_PIN            GPIO_PIN_6
#define DSI_BL_CTRL_GPIO_CLOCK_ENABLE() __HAL_RCC_GPIOI_CLK_ENABLE()

#define DSI_RESET_GPIO_PORT           GPIOD
#define DSI_RESET_GPIO_PIN            GPIO_PIN_5
#define DSI_RESET_GPIO_CLOCK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()

#define VSYNC 1
#define VBP   12
#define VFP   50
#define VACT  481
#define HSYNC 2
#define HBP   1
#define HFP   1
#define HACT  480

/* LCD interrupt priorities */
#define BSP_LCD_LTDC_IT_PRIORITY  0x0FUL /* Default is lowest priority level */
#define BSP_LCD_DSI_IT_PRIORITY   0x0FUL /* Default is lowest priority level */
#define BSP_LCD_DMA2D_IT_PRIORITY 0x0FUL /* Default is lowest priority level */

#if defined(CONFIG_PIXEL_FORMAT_ARGB8888)
#define LTDC_PIXEL_FORMAT       LTDC_PIXEL_FORMAT_ARGB8888
#define DSI_COLOR_CODING        DSI_RGB888
#define DMA2D_OUTPUT_COLOR_MODE DMA2D_OUTPUT_ARGB8888
#define DMA2D_INPUT_COLOR_MODE  DMA2D_INPUT_ARGB8888
#elif defined(CONFIG_PIXEL_FORMAT_RGB888)
#define LTDC_PIXEL_FORMAT       LTDC_PIXEL_FORMAT_RGB888
#define DSI_COLOR_CODING        DSI_RGB888
#define DMA2D_OUTPUT_COLOR_MODE DMA2D_OUTPUT_RGB888
#define DMA2D_INPUT_COLOR_MODE  DMA2D_INPUT_RGB888
#else   // CONFIG_PIXEL_FORMAT_RGB565
#define LTDC_PIXEL_FORMAT       LTDC_PIXEL_FORMAT_RGB565
#define DSI_COLOR_CODING        DSI_RGB565
#define DMA2D_OUTPUT_COLOR_MODE DMA2D_OUTPUT_RGB565
#define DMA2D_INPUT_COLOR_MODE  DMA2D_INPUT_RGB565
#endif

#define LCD_FRAME_BUFFER_SIZE (CONFIG_BYTE_PER_PIXEL * LCD_WIDTH * LCD_HEIGHT)

/* typedefs ------------------------------------------------------------------*/

typedef struct lcd_dev {
    hal_lcd_t lcd;
    LTDC_HandleTypeDef ltdc;
    DSI_HandleTypeDef dsi;
    DMA2D_HandleTypeDef dma2d;
} lcd_dev_t;

/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static lcd_dev_t lcd_dev;
static uint8_t *__fb = NULL;

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

void __dma2d_it_handler(uintptr_t arg)
{
    lcd_dev_t *dev = (lcd_dev_t *)arg;
    HAL_DMA2D_IRQHandler(&dev->dma2d);
}

void __ltdc_it_handler(uintptr_t arg)
{
    lcd_dev_t *dev = (lcd_dev_t *)arg;
    HAL_LTDC_IRQHandler(&dev->ltdc);
}

void __ltdc_er_it_handler(uintptr_t arg)
{
    lcd_dev_t *dev = (lcd_dev_t *)arg;
    HAL_LTDC_IRQHandler(&dev->ltdc);
}

void __dsi_it_handler(uintptr_t arg)
{
    lcd_dev_t *dev = (lcd_dev_t *)arg;
    HAL_DSI_IRQHandler(&dev->dsi);
}

void HAL_DMA2D_MspInit(DMA2D_HandleTypeDef *hdma2d)
{
    if (hdma2d->Instance == DMA2D) {
        /* Peripheral clock enable */
        __HAL_RCC_DMA2D_CLK_ENABLE();

        /* DMA2D interrupt Init */
        hal_it_connect(DMA2D_IRQn, __dma2d_it_handler, (uintptr_t)&lcd_dev);
        hal_it_setprio(DMA2D_IRQn, 0);
        hal_it_enable(DMA2D_IRQn);
    }
}

void HAL_DMA2D_MspDeInit(DMA2D_HandleTypeDef *hdma2d)
{
    if (hdma2d->Instance == DMA2D) {
        /* Peripheral clock disable */
        __HAL_RCC_DMA2D_CLK_DISABLE();

        /* DMA2D interrupt DeInit */
        hal_it_disable(DMA2D_IRQn);
    }
}

void HAL_DSI_MspInit(DSI_HandleTypeDef *hdsi)
{
    if (hdsi->Instance == DSI) {
        /* Peripheral clock enable */
        __HAL_RCC_DSI_CLK_ENABLE();

        /* DSI interrupt Init */
        hal_it_connect(DSI_IRQn, __dsi_it_handler, (uintptr_t)&lcd_dev);
        hal_it_setprio(DSI_IRQn, 0);
        hal_it_enable(DSI_IRQn);
    }
}

void HAL_DSI_MspDeInit(DSI_HandleTypeDef *hdsi)
{
    if (hdsi->Instance == DSI) {
        RCC_PeriphCLKInitTypeDef PLL3InitPeriph = {0};
        /* Switch to PLL3 before Disable */
        PLL3InitPeriph.PeriphClockSelection = RCC_PERIPHCLK_DSI;
        PLL3InitPeriph.DsiClockSelection = RCC_DSICLKSOURCE_PLL3;
        PLL3InitPeriph.PLL3.PLL3M = 4;
        PLL3InitPeriph.PLL3.PLL3N = 125;
        PLL3InitPeriph.PLL3.PLL3P = 8;
        PLL3InitPeriph.PLL3.PLL3Q = 8;
        PLL3InitPeriph.PLL3.PLL3R = 24;
        PLL3InitPeriph.PLL3.PLL3FRACN = 0;
        PLL3InitPeriph.PLL3.PLL3RGE = RCC_PLLVCIRANGE_1;
        PLL3InitPeriph.PLL3.PLL3ClockOut = RCC_PLL3_DIVR | RCC_PLL3_DIVP;
        PLL3InitPeriph.PLL3.PLL3Source = RCC_PLLSOURCE_HSE;
        (void)HAL_RCCEx_PeriphCLKConfig(&PLL3InitPeriph);

        /* Peripheral clock disable */
        __HAL_RCC_DSI_CLK_DISABLE();

        __HAL_RCC_DSI_FORCE_RESET();
        __HAL_RCC_DSI_RELEASE_RESET();

        /* DSI interrupt DeInit */
        hal_it_disable(DSI_IRQn);
    }
}

void HAL_LTDC_MspInit(LTDC_HandleTypeDef *hltdc)
{
    if (hltdc->Instance == LTDC) {
        /* Peripheral clock enable */
        __HAL_RCC_LTDC_CLK_ENABLE();

        /* LTDC interrupt Init */
        hal_it_connect(LTDC_IRQn, __ltdc_it_handler, (uintptr_t)&lcd_dev);
        hal_it_setprio(LTDC_IRQn, 0);
        hal_it_enable(LTDC_IRQn);

        hal_it_connect(LTDC_ER_IRQn, __ltdc_er_it_handler, (uintptr_t)&lcd_dev);
        hal_it_setprio(LTDC_ER_IRQn, 0);
        hal_it_enable(LTDC_ER_IRQn);
    }
}

void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc)
{
    if (hltdc->Instance == LTDC) {
        /* Peripheral clock disable */
        __HAL_RCC_LTDC_CLK_DISABLE();

        /* LTDC interrupt DeInit */
        hal_it_disable(LTDC_IRQn);
        hal_it_disable(LTDC_ER_IRQn);
    }
}

/**
 * @brief  Set the default DSI clock.
 * @param  None
 * @retval None
 */
static void LCD_Set_Default_Clock(void)
{
    RCC_PeriphCLKInitTypeDef DSIPHYInitPeriph;

    /* Switch to DSI PHY PLL clock */
    DSIPHYInitPeriph.PeriphClockSelection = RCC_PERIPHCLK_DSI;
    DSIPHYInitPeriph.DsiClockSelection = RCC_DSICLKSOURCE_DSIPHY;

    (void)HAL_RCCEx_PeriphCLKConfig(&DSIPHYInitPeriph);

    /* LCD Reset */
    HAL_Delay(11);
    HAL_GPIO_WritePin(DSI_RESET_GPIO_PORT, DSI_RESET_GPIO_PIN, GPIO_PIN_SET);
    HAL_Delay(150);
}

static int __lcd_clock_config(void)
{
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    /** Initializes the common periph clock
     */

    /* Start and configurre PLL3 */
    /* HSE = 16MHZ */
    /* 16/(M=4)   = 4MHz input (min) */
    /* 4*(N=125)  = 500MHz VCO (almost max) */
    /* 500/(P=8)  = 62.5 for DSI ie exactly the lane byte clock*/
    /* 500/(R=24) = 20.83 for LTDC exact match with DSI bandwidth */

    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_LTDC | RCC_PERIPHCLK_DSI;
    PeriphClkInit.DsiClockSelection = RCC_DSICLKSOURCE_PLL3;
    PeriphClkInit.LtdcClockSelection = RCC_LTDCCLKSOURCE_PLL3;
    PeriphClkInit.PLL3.PLL3Source = RCC_PLLSOURCE_HSE;
    PeriphClkInit.PLL3.PLL3M = 4;
    PeriphClkInit.PLL3.PLL3N = 125;
    PeriphClkInit.PLL3.PLL3P = 8;
    PeriphClkInit.PLL3.PLL3Q = 2;
    PeriphClkInit.PLL3.PLL3R = 24;
    PeriphClkInit.PLL3.PLL3RGE = RCC_PLLVCIRANGE_0;
    PeriphClkInit.PLL3.PLL3FRACN = 0;
    PeriphClkInit.PLL3.PLL3ClockOut = RCC_PLL3_DIVP | RCC_PLL3_DIVR;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
        return -1;
    }
    return 0;
}

static void __lcd_gpio_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* GPIO Ports Clock Enable */
    DSI_RESET_GPIO_CLOCK_ENABLE();
    DSI_BL_CTRL_GPIO_CLOCK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(DSI_RESET_GPIO_PORT, DSI_RESET_GPIO_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(DSI_BL_CTRL_GPIO_PORT, DSI_BL_CTRL_GPIO_PIN, GPIO_PIN_SET);   // Backlight on

    /* Configure DSI Reset pin */
    GPIO_InitStruct.Pin = DSI_RESET_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(DSI_RESET_GPIO_PORT, &GPIO_InitStruct);

    /* Configure LCD Backlight Pin */
    GPIO_InitStruct.Pin = DSI_BL_CTRL_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(DSI_BL_CTRL_GPIO_PORT, &GPIO_InitStruct);
}

static int __lcd_dma2d_init(lcd_dev_t *dev)
{
    DMA2D_HandleTypeDef *hdma2d = &dev->dma2d;

    hdma2d->Instance = DMA2D;
    hdma2d->Init.Mode = DMA2D_M2M;
    hdma2d->Init.ColorMode = DMA2D_OUTPUT_COLOR_MODE;
    hdma2d->Init.OutputOffset = 0;
    hdma2d->Init.BytesSwap = DMA2D_BYTES_REGULAR;
    hdma2d->Init.LineOffsetMode = DMA2D_LOM_PIXELS;

    hdma2d->LayerCfg[LTDC_LAYER_2].InputOffset = 0;
    hdma2d->LayerCfg[LTDC_LAYER_2].InputColorMode = DMA2D_INPUT_COLOR_MODE;
    hdma2d->LayerCfg[LTDC_LAYER_2].AlphaMode = DMA2D_NO_MODIF_ALPHA;
    hdma2d->LayerCfg[LTDC_LAYER_2].InputAlpha = 0xFF;
    hdma2d->LayerCfg[LTDC_LAYER_2].AlphaInverted = DMA2D_REGULAR_ALPHA;
    hdma2d->LayerCfg[LTDC_LAYER_2].RedBlueSwap = DMA2D_RB_REGULAR;

    if (HAL_DMA2D_Init(hdma2d) != HAL_OK) {
        return -1;
    }
    if (HAL_DMA2D_ConfigLayer(hdma2d, LTDC_LAYER_2) != HAL_OK) {
        return -2;
    }

    return 0;
}

static int __lcd_dsi_init(lcd_dev_t *dev)
{
    DSI_HandleTypeDef *hdsi = &dev->dsi;
    DSI_PLLInitTypeDef PLLInit = {0};
    DSI_HOST_TimeoutTypeDef HostTimeouts = {0};
    DSI_PHY_TimerTypeDef PhyTimings = {0};
    DSI_VidCfgTypeDef VidCfg = {0};

    /* DSI initialization */
    hdsi->Instance = DSI;
    hdsi->Init.AutomaticClockLaneControl = DSI_AUTO_CLK_LANE_CTRL_DISABLE;
    hdsi->Init.TXEscapeCkdiv = 4;
    hdsi->Init.NumberOfLanes = DSI_TWO_DATA_LANES;
    hdsi->Init.PHYFrequencyRange = DSI_DPHY_FRANGE_450MHZ_510MHZ;
    hdsi->Init.PHYLowPowerOffset = PHY_LP_OFFSSET_0_CLKP;

    PLLInit.PLLNDIV = 125;
    PLLInit.PLLIDF = DSI_PLL_IN_DIV4;
    PLLInit.PLLODF = DSI_PLL_OUT_DIV2;
    PLLInit.PLLVCORange = DSI_DPHY_VCO_FRANGE_800MHZ_1GHZ;
    PLLInit.PLLChargePump = DSI_PLL_CHARGE_PUMP_2000HZ_4400HZ;
    PLLInit.PLLTuning = DSI_PLL_LOOP_FILTER_2000HZ_4400HZ;
    if (HAL_DSI_Init(hdsi, &PLLInit) != HAL_OK) {
        return -1;
    }

    /* LCD configuration */
    HostTimeouts.TimeoutCkdiv = 1;
    HostTimeouts.HighSpeedTransmissionTimeout = 0;
    HostTimeouts.LowPowerReceptionTimeout = 0;
    HostTimeouts.HighSpeedReadTimeout = 0;
    HostTimeouts.LowPowerReadTimeout = 0;
    HostTimeouts.HighSpeedWriteTimeout = 0;
    HostTimeouts.HighSpeedWritePrespMode = DSI_HS_PM_DISABLE;
    HostTimeouts.LowPowerWriteTimeout = 0;
    HostTimeouts.BTATimeout = 0;
    if (HAL_DSI_ConfigHostTimeouts(hdsi, &HostTimeouts) != HAL_OK) {
        return -1;
    }
    PhyTimings.ClockLaneHS2LPTime = 11;
    PhyTimings.ClockLaneLP2HSTime = 40;
    PhyTimings.DataLaneHS2LPTime = 12;
    PhyTimings.DataLaneLP2HSTime = 23;
    PhyTimings.DataLaneMaxReadTime = 0;
    PhyTimings.StopWaitTime = 7;
    if (HAL_DSI_ConfigPhyTimer(hdsi, &PhyTimings) != HAL_OK) {
        return -1;
    }
    if (HAL_DSI_ConfigFlowControl(hdsi, DSI_FLOW_CONTROL_BTA) != HAL_OK) {
        return -1;
    }
    if (HAL_DSI_ConfigErrorMonitor(hdsi, HAL_DSI_ERROR_NONE) != HAL_OK) {
        return -1;
    }

    /* Configure the DSI for Video mode */
    VidCfg.ColorCoding = DSI_COLOR_CODING;
    VidCfg.LooselyPacked = DSI_LOOSELY_PACKED_DISABLE;
    VidCfg.Mode = DSI_VID_MODE_BURST;
    VidCfg.PacketSize = LCD_WIDTH;
    VidCfg.NumberOfChunks = 0;
    VidCfg.NullPacketSize = 0;
    VidCfg.HSPolarity = DSI_HSYNC_ACTIVE_HIGH;
    VidCfg.VSPolarity = DSI_VSYNC_ACTIVE_HIGH;
    VidCfg.DEPolarity = DSI_DATA_ENABLE_ACTIVE_HIGH;
    VidCfg.HorizontalSyncActive = HSYNC * 3;
    VidCfg.HorizontalBackPorch = HBP * 3;
    VidCfg.HorizontalLine = (HACT + HSYNC + HBP + HFP) * 3;
    VidCfg.VerticalSyncActive = VSYNC;
    VidCfg.VerticalBackPorch = VBP;
    VidCfg.VerticalFrontPorch = VFP;
    VidCfg.VerticalActive = VACT;
    VidCfg.LPCommandEnable = DSI_LP_COMMAND_DISABLE;
    VidCfg.LPLargestPacketSize = 0;
    VidCfg.LPVACTLargestPacketSize = 0;
    VidCfg.LPHorizontalFrontPorchEnable = DSI_LP_HFP_ENABLE;
    VidCfg.LPHorizontalBackPorchEnable = DSI_LP_HBP_ENABLE;
    VidCfg.LPVerticalActiveEnable = DSI_LP_VACT_ENABLE;
    VidCfg.LPVerticalFrontPorchEnable = DSI_LP_VFP_ENABLE;
    VidCfg.LPVerticalBackPorchEnable = DSI_LP_VBP_ENABLE;
    VidCfg.LPVerticalSyncActiveEnable = DSI_LP_VSYNC_ENABLE;
    VidCfg.FrameBTAAcknowledgeEnable = DSI_FBTAA_ENABLE;
    if (HAL_DSI_ConfigVideoMode(hdsi, &VidCfg) != HAL_OK) {
        return -1;
    }
    if (HAL_DSI_SetGenericVCID(hdsi, 0) != HAL_OK) {
        return -1;
    }

    LCD_Set_Default_Clock();

    return 0;
}

static int __lcd_ltdc_init(lcd_dev_t *dev)
{
    LTDC_HandleTypeDef *hltdc = &dev->ltdc;
    LTDC_LayerCfgTypeDef pLayerCfg = {0};

    /* LTDC initialization */
    hltdc->Instance = LTDC;
    hltdc->Init.HSPolarity = LTDC_HSPOLARITY_AH;
    hltdc->Init.VSPolarity = LTDC_VSPOLARITY_AH;
    hltdc->Init.DEPolarity = LTDC_DEPOLARITY_AL;
    hltdc->Init.PCPolarity = LTDC_PCPOLARITY_IPC;
    hltdc->Init.HorizontalSync = HSYNC - 1;
    hltdc->Init.VerticalSync = 0;
    hltdc->Init.AccumulatedHBP = HSYNC + HBP - 1;
    hltdc->Init.AccumulatedVBP = 12;
    hltdc->Init.AccumulatedActiveW = HACT + HBP + HSYNC - 1;
    hltdc->Init.AccumulatedActiveH = 493;
    hltdc->Init.TotalWidth = HACT + HBP + HFP + HSYNC - 1;
    hltdc->Init.TotalHeigh = 543;
    hltdc->Init.Backcolor.Red = 0;
    hltdc->Init.Backcolor.Green = 0;
    hltdc->Init.Backcolor.Blue = 0;
    if (HAL_LTDC_Init(hltdc) != HAL_OK) {
        return -1;
    }

    /* LTDC layer configuration */
    pLayerCfg.WindowX0 = 0;
    pLayerCfg.WindowX1 = LCD_WIDTH;
    pLayerCfg.WindowY0 = 0;
    pLayerCfg.WindowY1 = LCD_HEIGHT;
    pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT;
    pLayerCfg.Alpha = 0xFF;
    pLayerCfg.Alpha0 = 0;
    pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA;
    pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA;
    pLayerCfg.FBStartAdress = (uint32_t)__fb;
    pLayerCfg.ImageWidth = LCD_WIDTH;
    pLayerCfg.ImageHeight = LCD_HEIGHT;
    pLayerCfg.Backcolor.Blue = 0;
    pLayerCfg.Backcolor.Green = 0;
    pLayerCfg.Backcolor.Red = 0;
    if (HAL_LTDC_ConfigLayer(hltdc, &pLayerCfg, LTDC_LAYER_1) != HAL_OK) {
        return -2;
    }

    return 0;
}

static int __lcd_panel_config(lcd_dev_t *dev)
{
    DSI_HandleTypeDef *hdsi = &dev->dsi;

    if (HAL_DSI_Start(hdsi) != HAL_OK)
        return 1;

    /* CMD Mode */
    uint8_t InitParam1[3] = {0xFF, 0x83, 0x79};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 3, 0xB9, InitParam1) != HAL_OK)
        return 1;

    /* SETPOWER */
    uint8_t InitParam3[16] = {0x44, 0x1C, 0x1C, 0x37, 0x57, 0x90, 0xD0, 0xE2,
                              0x58, 0x80, 0x38, 0x38, 0xF8, 0x33, 0x34, 0x42};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 16, 0xB1, InitParam3) != HAL_OK)
        return 2;

    /* SETDISP */
    uint8_t InitParam4[9] = {0x80, 0x14, 0x0C, 0x30, 0x20, 0x50, 0x11, 0x42, 0x1D};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 9, 0xB2, InitParam4) != HAL_OK)
        return 3;

    /* Set display cycle timing */
    uint8_t InitParam5[10] = {0x01, 0xAA, 0x01, 0xAF, 0x01, 0xAF, 0x10, 0xEA, 0x1C, 0xEA};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 10, 0xB4, InitParam5) != HAL_OK)
        return 4;

    /* SETVCOM */
    uint8_t InitParam60[4] = {00, 00, 00, 0xC0};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 4, 0xC7, InitParam60) != HAL_OK)
        return 5;

    /* Set Panel Related Registers */
    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xCC, 0x02) != HAL_OK)
        return 6;

    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xD2, 0x77) != HAL_OK)
        return 7;

    uint8_t InitParam50[37] = {0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x08, 0x32, 0x10, 0x01,
                               0x00, 0x01, 0x03, 0x72, 0x03, 0x72, 0x00, 0x08, 0x00, 0x08,
                               0x33, 0x33, 0x05, 0x05, 0x37, 0x05, 0x05, 0x37, 0x0A, 0x00,
                               0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x0E};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 37, 0xD3, InitParam50) != HAL_OK)
        return 8;

    uint8_t InitParam51[34] = {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19,
                               0x19, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x01, 0x00,
                               0x03, 0x02, 0x05, 0x04, 0x07, 0x06, 0x23, 0x22, 0x21,
                               0x20, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 34, 0xD5, InitParam51) != HAL_OK)
        return 9;

    uint8_t InitParam52[35] = {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x18,
                               0x18, 0x19, 0x19, 0x18, 0x18, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03,
                               0x00, 0x01, 0x20, 0x21, 0x22, 0x23, 0x18, 0x18, 0x18, 0x18};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 35, 0xD6, InitParam52) != HAL_OK)
        return 10;

    /* SET GAMMA */
    uint8_t InitParam8[42] = {0x00, 0x16, 0x1B, 0x30, 0x36, 0x3F, 0x24, 0x40, 0x09, 0x0D, 0x0F,
                              0x18, 0x0E, 0x11, 0x12, 0x11, 0x14, 0x07, 0x12, 0x13, 0x18, 0x00,
                              0x17, 0x1C, 0x30, 0x36, 0x3F, 0x24, 0x40, 0x09, 0x0C, 0x0F, 0x18,
                              0x0E, 0x11, 0x14, 0x11, 0x12, 0x07, 0x12, 0x14, 0x18};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xE0, InitParam8) != HAL_OK)
        return 11;

    uint8_t InitParam44[3] = {0x2C, 0x2C, 00};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 3, 0xB6, InitParam44) != HAL_OK)
        return 12;

    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, 0x00) != HAL_OK)
        return 13;

    uint8_t InitParam14[] = {0x01, 0x00, 0x07, 0x0F, 0x16, 0x1F, 0x27, 0x30, 0x38, 0x40, 0x47,
                             0x4E, 0x56, 0x5D, 0x65, 0x6D, 0x74, 0x7D, 0x84, 0x8A, 0x90, 0x99,
                             0xA1, 0xA9, 0xB0, 0xB6, 0xBD, 0xC4, 0xCD, 0xD4, 0xDD, 0xE5, 0xEC,
                             0xF3, 0x36, 0x07, 0x1C, 0xC0, 0x1B, 0x01, 0xF1, 0x34, 0x00};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xC1, InitParam14) != HAL_OK)
        return 14;

    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, 0x01) != HAL_OK)
        return 15;

    uint8_t InitParam15[] = {0x00, 0x08, 0x0F, 0x16, 0x1F, 0x28, 0x31, 0x39, 0x41, 0x48, 0x51,
                             0x59, 0x60, 0x68, 0x70, 0x78, 0x7F, 0x87, 0x8D, 0x94, 0x9C, 0xA3,
                             0xAB, 0xB3, 0xB9, 0xC1, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xEE, 0xF5,
                             0x3B, 0x1A, 0xB6, 0xA0, 0x07, 0x45, 0xC5, 0x37, 0x00};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xC1, InitParam15) != HAL_OK)
        return 16;

    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, 0x02) != HAL_OK)
        return 17;

    uint8_t InitParam20[42] = {0x00, 0x09, 0x0F, 0x18, 0x21, 0x2A, 0x34, 0x3C, 0x45, 0x4C, 0x56,
                               0x5E, 0x66, 0x6E, 0x76, 0x7E, 0x87, 0x8E, 0x95, 0x9D, 0xA6, 0xAF,
                               0xB7, 0xBD, 0xC5, 0xCE, 0xD5, 0xDF, 0xE7, 0xEE, 0xF4, 0xFA, 0xFF,
                               0x0C, 0x31, 0x83, 0x3C, 0x5B, 0x56, 0x1E, 0x5A, 0xFF};
    if (HAL_DSI_LongWrite(hdsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xC1, InitParam20) != HAL_OK)
        return 18;

    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, 0x00) != HAL_OK)
        return 19;

    /* Exit Sleep Mode*/
    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P0, 0x11, 0x00) != HAL_OK)
        return 20;

    HAL_Delay(50);

    /* Clear LCD_FRAME_BUFFER */
    memset((uint8_t *)__fb, 0x00, LCD_FRAME_BUFFER_SIZE);

    /* Display On */
    if (HAL_DSI_ShortWrite(hdsi, 0, DSI_DCS_SHORT_PKT_WRITE_P0, 0x29, 0x00) != HAL_OK)
        return 21;

    HAL_Delay(50);

    /* All setting OK */
    return 0;
}

static int __stm32_lcd_init(lcd_dev_t *dev)
{
    int ret;

    __fb = (uint8_t *)malloc(LCD_FRAME_BUFFER_SIZE);
    if (__fb == NULL) {
        return -1;
    }

    if ((ret = __lcd_clock_config()) != 0) {
        return ret;
    }

    __lcd_gpio_init();

    if ((ret = __lcd_dma2d_init(dev)) != 0) {
        return ret;
    }
    if ((ret = __lcd_dsi_init(dev)) != 0) {
        return ret;
    }
    if ((ret = __lcd_ltdc_init(dev)) != 0) {
        return ret;
    }
    if ((ret = __lcd_panel_config(dev)) != 0) {
        return ret;
    }
    return 0;
}

static int __lcd_setpixel(hal_lcd_t *lcd, uint32_t x, uint32_t y, uint32_t color)
{
    lcd_dev_t *dev = container_of(lcd, lcd_dev_t, lcd);
    uint32_t StartAddress = 0;
    LTDC_HandleTypeDef *hltdc = &dev->ltdc;

    /* Set the start address */
    StartAddress =
        (hltdc->LayerCfg[0].FBStartAdress + (CONFIG_BYTE_PER_PIXEL * (y * LCD_WIDTH + x)));

    /* Fill Point */
#if (CONFIG_BYTE_PER_PIXEL == 4)
    *(__IO uint32_t *)(StartAddress) = color;
#elif (CONFIG_BYTE_PER_PIXEL == 3)
    *(__IO uint8_t *)(StartAddress) = color & 0xFF;
    *(__IO uint8_t *)(StartAddress + 1) = (color >> 8) & 0xFF;
    *(__IO uint8_t *)(StartAddress + 2) = (color >> 16) & 0xFF;
#elif (CONFIG_BYTE_PER_PIXEL == 2)
    *(__IO uint16_t *)(StartAddress) = color;
#else
    *(__IO uint8_t *)(StartAddress) = color;
#endif

    return 0;
}

static int __lcd_ioctl(hal_lcd_t *lcd, int cmd, va_list valist)
{
    switch (cmd) {
    case HAL_LCD_CMD_BLK_TOGGLE:
        HAL_GPIO_TogglePin(DSI_BL_CTRL_GPIO_PORT, DSI_BL_CTRL_GPIO_PIN);
        break;
    default:
        return -ENOSYS;
    }

    return 0;
}

static const hal_lcd_methods_t lcd_methods = {
    .setpixel = __lcd_setpixel,
    .ioctl = __lcd_ioctl,
};

static int __lcd_open(hal_dev_t *dev, int oflag, va_list valist)
{
    lcd_dev_t *lcd = (lcd_dev_t *)dev;

    if (__stm32_lcd_init(lcd) != 0) {
        return -1;
    }

    return 0;
}

static hal_dev_ops_t dev_ops = {
    .open = __lcd_open,
};

static int stm32_lcd_init(void)
{
    if (hal_lcd_register(&lcd_dev.lcd, "lcd", &lcd_methods, 0)) {
        return -1;
    }
    if (hal_dev_init((hal_dev_t *)&lcd_dev, &dev_ops) != 0) {
        return -2;
    }
    return 0;
}

MODULE_INIT(driver, stm32_lcd_init);
